/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Canadian Weather.
 *
 * The Initial Developer of the Original Code is
 *   George Bradt.
 *
 * Portions created by the Initial Developer are Copyright (C) 2011
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   George Bradt.
 *
 * Credits:
 *   Sun icons from Icons-Land at http://www.icons-land.com/vista-weather-icons.php
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var CanWeather = {
    // Import the APIs we need.
    widgets: require('widget'),
    windows: require("windows"),
    panels: require('panel'),
    data: require('self').data,
    simpleStorage: require('simple-storage'),
    timers: require('timer'),
    requests: require('request'),
    notifications: require('notifications'),
    textToXml: require('textToXml'),
    parseXml: require('parseXml'),
    //observerService: require('observer-service'),

    // My widget and panels.
    myWidget: null,
    optionsPanel: null,
    conditionsPanel: null,
    warningPanel: null,
    errorPanel: null,

    // Miscellaneous.
    timerId: 0,                    // setInterval id
    errorMessage: '',              // message to error panel
    retryOnNetworkError: true,     // retry data file request on a network error (status code = 0)
    combinedData: '',              // includes all current warnings - to conditions panel (which always shows all current warnings)
    currentWarningsToShow: [],     // warnings to display in warnings panel
    previousWarnings: [],          // warnings from previous fetch
    previousIconCode: '99',        // icon code from previous fetch (init value is flag icon)
    previousBarDisplayString: '',  // barDisplayString from previous fetch
    imageShowing: true,            // is a condition image currently displayed in widget?
    defaultImageUrl: "",           // URL of the flag image file
    iconTooltip: "",               // condition if 'show condition when hovering condition icon' enabled
    textTooltip: "",               // observed time/date

    // This should only run at install.
    initializeOptions: function() {
        console.log('Canadian Weather: initializing options');

        CanWeather.simpleStorage.storage.options = {
            language: 'e',  // e=English, f=French
            location: {
                provinceIndex: 0,
                provinceCode: '',
                siteIndex: 0,
                siteCode: ''
            },
            frequencyMilli: 3600000,
            toolbarDisplay: {
                showTemperature: true,
                showWindChill: false,
                showHumidex: false,
                showCondition: false,
                showConditionIcon: false,
                showConditionIconTooltip: false
            },
            toolbarStyle: {
                bold: false,
                italic: false,
                color: {
                    normal: ['', ''],
                    priorityLow: ['', ''],
                    priorityMedium: ['', ''],
                    priorityHigh: ['', ''],
                    priorityUrgent: ['', '']
                }
            },
            toolbarDisplayEnabled: true,    // text portion
            toolbarColorEnabled: false,
            conditionsPanelEnabled: false,
            warningPanelEnabled: true,
            showWarningOnlyOnce: true
        };
    },

    // This widget displays an image and/or weather data of variable length, in the bar.
    createWidget: function() {
        CanWeather.myWidget = CanWeather.widgets.Widget({
        id: 'canweather',
        label: 'Canadian Weather',
        width: 48,
        content: '<div id="canw-widgetDiv" style="position: relative; min-height: 16px;"><img id="canw-widgetImage" src="' + CanWeather.data.url('images/condition/99.png') +
                 '" style="cursor:pointer;"/><span id="canw-widgetText" style="position: fixed; right: 0px; vertical-align: top; white-space: pre; padding-left: 5px; padding-right: 5px; font-family: Segoe UI; font-size: 12px; cursor: pointer;"></span></div>',
        contentScriptWhen: 'end',
        contentScriptFile: CanWeather.data.url('widget/widget.js')
        });

        // Hack required because there is no way to replicate dynamic widget content to new windows (SDK 1.0b5).
        CanWeather.myWidget.port.on('newWindow', function() {
            if ( CanWeather.simpleStorage.storage.options.location.siteCode &&
                 CanWeather.windows.browserWindows.length > 1 ) {
                var forceRebuild = true;
                CanWeather.setToolbarIcon(forceRebuild);
                CanWeather.setToolbarText(forceRebuild);
                CanWeather.styleToolbarText();
            }
        });

        // Set the widget width.
        CanWeather.myWidget.port.on('textWidth', function(aWidth) {
            CanWeather.myWidget.width = ( CanWeather.imageShowing ) ? aWidth + 48 : aWidth;
        });

        // Valid warning priorities are 'Low', 'Medium', 'High' and 'Urgent'.
        CanWeather.myWidget.port.on('unexpectedPriorityError', function(aUnexpectedPriority) {
            CanWeather.errorMessage = 'A warning has an unexpected priority value: ' + aUnexpectedPriority;
            console.log('Canadian Weather: ' + CanWeather.errorMessage);
            CanWeather.errorPanel.width = 300;
            CanWeather.errorPanel.height = 170;
            CanWeather.errorPanel.show();
        });

        CanWeather.myWidget.port.on('right-click', function() {
            CanWeather.optionsPanel.show();
        });

        CanWeather.myWidget.port.on('left-click', function() {
            if ( CanWeather.simpleStorage.storage.options.conditionsPanelEnabled ) {
                if ( CanWeather.combinedData ) {
                    CanWeather.conditionsPanel.show();
                }
                else {
                    var message = 'There is no data to show yet';
                    CanWeather.generateNotification(message);
                }
            }
        });
        
        CanWeather.myWidget.port.on('setTooltip', function(aTargetNodeName) {
            if ( CanWeather.simpleStorage.storage.options.toolbarDisplay.showConditionIconTooltip &&
                 aTargetNodeName == 'IMG' ) {
                CanWeather.myWidget.tooltip = CanWeather.iconTooltip;
            }
            else {
                CanWeather.myWidget.tooltip = CanWeather.textTooltip;
            }
        });

    },

    // We have four panels.
    createPanels: function() {
        // Options panel will show when right-clicking the widget.
        CanWeather.optionsPanel = CanWeather.panels.Panel({
            width: 650,
            height: 510,
            contentURL: CanWeather.data.url('options/options.html'),
            contentScriptFile: CanWeather.data.url('options/options.js'),
            contentScriptWhen: 'end',
            onShow: function() {
                // Send options to content script options.js.
                this.port.emit('show', CanWeather.simpleStorage.storage.options);
            }
        });

        // If something has gone wrong with the toolbar display, attempt to fix.
        CanWeather.optionsPanel.port.on('rebuild-click', function() {
            var forceRebuild = true;
            CanWeather.setToolbarIcon(forceRebuild);
            CanWeather.setToolbarText(forceRebuild);
            CanWeather.styleToolbarText();
        });

        // Handle user-defined events emitted from content script options.js.
        CanWeather.optionsPanel.port.on('ok-click', function(aOptions) {
            var previousFrequency = CanWeather.simpleStorage.storage.options.frequencyMilli;
            var newFrequency = aOptions.frequencyMilli;
            CanWeather.simpleStorage.storage.options = aOptions;
            CanWeather.optionsPanel.hide();
            if ( CanWeather.simpleStorage.storage.options.location.siteCode ) {
                CanWeather.fetchWeatherData();

                if ( !CanWeather.timerId ) {
                    CanWeather.timerId = CanWeather.timers.setInterval(function() { CanWeather.fetchWeatherData(); }, newFrequency);
                }
                else if ( newFrequency != previousFrequency ) {
                    CanWeather.timers.clearInterval(CanWeather.timerId);
                    CanWeather.timerId = CanWeather.timers.setInterval(function() { CanWeather.fetchWeatherData(); }, newFrequency);
                }
            }
        });

        CanWeather.optionsPanel.port.on('cancel-click', function() {
            CanWeather.errorPanel.hide();
            CanWeather.optionsPanel.hide();
        });

        CanWeather.optionsPanel.port.on('error', function(aErrorMessage) {
            console.log('Canadian Weather: ' + aErrorMessage);
            CanWeather.errorMessage = aErrorMessage;
            CanWeather.errorPanel.width = 200;
            CanWeather.errorPanel.height = 140;
            CanWeather.errorPanel.show();
        });

        // Conditions panel will show when left-clicking the widget,
        // if it has been enabled in options
        CanWeather.conditionsPanel = CanWeather.panels.Panel({
            width: 750,
            height: 450,
            contentURL: CanWeather.data.url('conditions/conditions.html'),
            contentScriptFile: CanWeather.data.url('conditions/conditions.js'),
            contentScriptWhen: 'end',
            onShow: function() {
                // Send the current conditions to content script conditions.js.
                this.port.emit('show', CanWeather.combinedData);
            }
        });

        // Handle user-defined events emitted from content script conditions.js.
        CanWeather.conditionsPanel.port.on('ok-click', function() {
            CanWeather.conditionsPanel.hide();
        });

        // Warnings panel will show when a weather alert is issued for the tracked location.
        // Triggered after the site data request completes.
        CanWeather.warningPanel = CanWeather.panels.Panel({
            width: 500,
            height: 320,
            contentURL: CanWeather.data.url('warning/warning.html'),
            contentScriptFile: CanWeather.data.url('warning/warning.js'),
            contentScriptWhen: 'end',
            onShow: function() {
                // Send the new warnings to content script warning.js.
                this.port.emit('show', CanWeather.currentWarningsToShow);
            }
        });

        // Handle user-defined events emitted from content script warning.js.
        CanWeather.warningPanel.port.on('ok-click', function() {
            CanWeather.warningPanel.hide();
        });

        // Error panel will show when a content script detects an error.
        CanWeather.errorPanel = CanWeather.panels.Panel({
            width: 200,
            height: 100,
            contentURL: CanWeather.data.url('error/error.html'),
            contentScriptFile: CanWeather.data.url('error/error.js'),
            contentScriptWhen: 'end',
            onShow: function() {
                // Send the error message to content script error.js.
                this.port.emit('show', CanWeather.errorMessage);
            }
        });

        // Handle user-defined events emitted from content script error.js.
        CanWeather.errorPanel.port.on('ok-click', function() {
            CanWeather.errorPanel.hide();
        });
    },

    // Request XML weather data file for the site chosen in options.
    fetchWeatherData: function() {
        // Make sure user has set some display option.
        if ( CanWeather.simpleStorage.storage.options.toolbarDisplayEnabled ||
             CanWeather.simpleStorage.storage.options.toolbarDisplay.showConditionIcon ||
             CanWeather.simpleStorage.storage.options.conditionsPanelEnabled ||
             CanWeather.simpleStorage.storage.options.warningPanelEnabled ) {
            var prov = CanWeather.simpleStorage.storage.options.location.provinceCode;
            var site = CanWeather.simpleStorage.storage.options.location.siteCode;
            var language = CanWeather.simpleStorage.storage.options.language;

            var weatherDataRequest = CanWeather.requests.Request({
                url: 'http://dd.weatheroffice.ec.gc.ca/citypage_weather/xml/' + prov + '/' + site + '_' + language + '.xml',
                onComplete: function (response) {
                    if ( response.statusText == 'OK' ) {
                        CanWeather.xhrSuccess(response.text);
                    }
                    else {
                        CanWeather.xhrFail(response.status);
                    }
                }
            }).get();
        }
        else {
            CanWeather.imageShowing = true;
            var iconCode = '99';
            
            if ( iconCode != CanWeather.previousIconCode ) {
                var condition = '';
                CanWeather.myWidget.port.emit('showImage', iconCode, condition, CanWeather.defaultImageUrl);
                //console.log('*** fetchWeatherData --- Image shown: ' + iconCode)
                CanWeather.previousIconCode = iconCode;
            }
            
            CanWeather.myWidget.port.emit('removeText');
            //console.log('*** fetchWeatherData --- Text removed')
            CanWeather.previousBarDisplayString = '';
            var message = 'Warning! You have no display options enabled';
            CanWeather.generateNotification(message);
            console.log('Canadian Weather: ' + message);
        }
    },

    // Site data retrieved.
    xhrSuccess: function(aResponse) {
        CanWeather.retryOnNetworkError = true;

        // Due to bug 611042 (request.response.xml pulled from request module api)
        // we convert the response text to an XML object ourselves.
        var xml = new CanWeather.textToXml.convert(aResponse);
        console.log('Canadian Weather: site data retrieved for ' +
                    xml.rootElement.childElement('location').childElement('name').text +
                    ' at ' + new Date().toLocaleTimeString());

        // Gather all of our data.
        CanWeather.combinedData = CanWeather.parseXml.parse(xml, CanWeather.simpleStorage.storage.options);
        
        var forceRebuild = false;
        CanWeather.setToolbarIcon(forceRebuild);
        CanWeather.setToolbarText(forceRebuild);
        CanWeather.styleToolbarText();
        CanWeather.manageWarnings();

        // Date the current conditions were observed.
        CanWeather.textTooltip = 'Observed: ' + CanWeather.combinedData.currentConditions.date;
        CanWeather.iconTooltip = CanWeather.combinedData.currentConditions.condition;
    },

    // Display the toolbar condition icon if requested.
    setToolbarIcon: function(aForceRebuild) {
        if ( CanWeather.simpleStorage.storage.options.toolbarDisplay.showConditionIcon ) {
            if ( !CanWeather.imageShowing ) {
                CanWeather.myWidget.width += 48;
                CanWeather.imageShowing = true;
            }
            
            var iconCode = CanWeather.combinedData.currentConditions.iconCode;
            
            if ( aForceRebuild || iconCode != CanWeather.previousIconCode ) {
                var condition = CanWeather.combinedData.currentConditions.condition;
                CanWeather.myWidget.port.emit('showImage', iconCode, condition, CanWeather.defaultImageUrl);
                //console.log('*** setToolbarIcon(show icon) --- Image shown: ' + iconCode)
                CanWeather.previousIconCode = iconCode;
                //console.log('Environment Canada icon code is ' + iconCode + ', condition is ' + condition);
            }
        }
        else if ( !CanWeather.simpleStorage.storage.options.toolbarDisplayEnabled ) {
            // Have to show something. Flag.
            if ( !CanWeather.imageShowing ) {
                CanWeather.myWidget.width += 48;
                CanWeather.imageShowing = true;
            }
            
            iconCode = '99';
            
            if ( iconCode != CanWeather.previousIconCode ) {
                condition = '';
                CanWeather.myWidget.port.emit('showImage', iconCode, condition, CanWeather.defaultImageUrl);
                //console.log('*** setToolbarIcon(no icon or text) --- Image shown: ' + iconCode)
                CanWeather.previousIconCode = iconCode;
            }
        }
        else {
            if ( CanWeather.imageShowing ) {
                CanWeather.myWidget.width -= 48;
                CanWeather.imageShowing = false;
            }
            
            CanWeather.myWidget.port.emit('hideImage');
            //console.log('*** setToolbarIcon(only text) --- Image hidden')
            CanWeather.previousIconCode = '';
        }
    },
    
    // Display the toolbar condition text if requested.
    setToolbarText: function(aForceRebuild) {
        if ( CanWeather.simpleStorage.storage.options.toolbarDisplayEnabled ) {
            if ( aForceRebuild || CanWeather.combinedData.barDisplayString != CanWeather.previousBarDisplayString ) {
                // Auto-adjusts widget width.
                CanWeather.myWidget.port.emit('removeText');
                //console.log('*** setToolbarText(prepare insert) --- Text removed')
                
                // Insert the string to be displayed, and determine it's width in pixels.
                // Auto-adjusts widget width.
                CanWeather.myWidget.port.emit('insertTextAndMeasure',
                                              CanWeather.combinedData.barDisplayString);
                
                //console.log('*** setToolbarText --- Text inserted: ' + CanWeather.combinedData.barDisplayString)
                CanWeather.previousBarDisplayString = CanWeather.combinedData.barDisplayString;
            }
        }
        else {
            // Auto-adjusts widget width.
            CanWeather.myWidget.port.emit('removeText');
            //console.log('*** setToolbarText(no text) --- Text removed')
            CanWeather.previousBarDisplayString = '';
        }
    },
    
    // Style (font, color) the toolbar condition text.
    styleToolbarText: function() {
        if ( CanWeather.simpleStorage.storage.options.toolbarDisplayEnabled ) {
            // Pass options (as of SDK 1.0rc1, cannot directly pass simple-storage item to widget content script)
            // and warnings to set widget colors if enabled.
            CanWeather.myWidget.port.emit('styleText',
                                          JSON.parse(JSON.stringify(CanWeather.simpleStorage.storage.options)),
                                          CanWeather.combinedData.warnings);
            //console.log('*** styleToolbarText')
        }
    },
    
    // Do not show a previously shown warning in the warning panel (if that option enabled).
    // All warnings are always shown in the conditions panel.
    manageWarnings: function() {
        CanWeather.currentWarningsToShow = [];

        if ( CanWeather.simpleStorage.storage.options.warningPanelEnabled && CanWeather.combinedData.warnings.length > 0 ) {
            // url.
            CanWeather.currentWarningsToShow.push(CanWeather.combinedData.warnings[0]);

            if ( CanWeather.simpleStorage.storage.options.showWarningOnlyOnce ) {
                var match;
                
                // Bypass first element in combinedData.warnings, which is the url to EC warning details.
                for ( let i = 1; i < CanWeather.combinedData.warnings.length; i++ ) {
                    match = false;
                    
                    for ( let j = 0; j < CanWeather.previousWarnings.length; j++ ) {
                        // Check if this warning has already been displayed.
                        if ( CanWeather.combinedData.warnings[i].location == CanWeather.previousWarnings[j].location &&
                             CanWeather.combinedData.warnings[i].type == CanWeather.previousWarnings[j].type &&
                             CanWeather.combinedData.warnings[i].priority == CanWeather.previousWarnings[j].priority &&
                             CanWeather.combinedData.warnings[i].description == CanWeather.previousWarnings[j].description ) {
                            // Yes it has.
                            match = true;
                            break;
                        }
                    }

                    if ( !match ) {
                        // New one, show it.
                        CanWeather.currentWarningsToShow.push(CanWeather.combinedData.warnings[i]);
                    }
                }
            }
            else {
                // Show them all.
                CanWeather.currentWarningsToShow = CanWeather.combinedData.warnings;
            }

            // Don't include the url element.
            CanWeather.previousWarnings = CanWeather.combinedData.warnings.slice(1);
            
            if ( CanWeather.currentWarningsToShow.length > 1 ) {
                CanWeather.warningPanel.show();
            }
        }
        else {
            CanWeather.previousWarnings = [];    
        }
    },

    // Site data could not be retrieved.
    xhrFail: function(aStatusCode) {
        if ( aStatusCode == '0' ) {
            // Network error.
            console.log('Canadian Weather: network error, could not access Environment Canada server');

            if ( CanWeather.retryOnNetworkError ) {
                // Wait 5 minutes and retry the request.
                CanWeather.retryOnNetworkError = false
                console.log('Canadian Weather: retrying...');
                CanWeather.timers.setTimeout(function() { CanWeather.fetchWeatherData(); }, 300000);
            }
            else {
                CanWeather.retryOnNetworkError = true;
                CanWeather.errorMessage = 'Network error! Could not access Environment Canada server';
                CanWeather.errorPanel.width = 300;
                CanWeather.errorPanel.height = 170;
                CanWeather.errorPanel.show();
            }
        }
        else {
            CanWeather.retryOnNetworkError = true;
            console.log('Canadian Weather: site data could not be retrieved. Status code: ' + aStatusCode);
            CanWeather.errorMessage = 'Site data could not be retrieved. Status code: ' + aStatusCode;
            CanWeather.errorPanel.width = 250;
            CanWeather.errorPanel.height = 170;
            CanWeather.errorPanel.show();
        }
    },

    // Messages that don't rate an error panel.
    generateNotification: function(aMessage) {
        CanWeather.notifications.notify({
            title: 'Canadian Weather',
            text: aMessage,
            iconURL: CanWeather.data.url('images/sunny48.png')
        });
    }
    
    // If Customize Toolbar window was just closed, rebuild content.
    //domWindowClosed: function(subject, data) {
    //    if ( subject.name == 'CustomizeToolbar' ) {
    //        var forceRebuild = true;
    //        CanWeather.setToolbarIcon(forceRebuild);
    //        CanWeather.setToolbarText(forceRebuild);
    //        CanWeather.styleToolbarText();
    //    }
    //}
};

// This function will be called when the add-on is loaded.
exports.main = function() {
    if ( !CanWeather.simpleStorage.storage.options ) {
        // This should only occur once.
        CanWeather.initializeOptions();
    }
    else {
        // New options added since original initialization.
        if ( !CanWeather.simpleStorage.storage.options.toolbarStyle ) {
            CanWeather.simpleStorage.storage.options.toolbarStyle.bold = false;
            CanWeather.simpleStorage.storage.options.toolbarStyle.italic = false;
            CanWeather.simpleStorage.storage.options.toolbarStyle.color = {
                normal: ['', ''],
                priorityLow: ['', ''],
                priorityMedium: ['', ''],
                priorityHigh: ['', ''],
                priorityUrgent: ['', '']
            };
            CanWeather.simpleStorage.storage.options.toolbarColorEnabled = false;
        }
        else if ( !CanWeather.simpleStorage.storage.options.toolbarStyle.color.normal ) {
            CanWeather.simpleStorage.storage.options.toolbarStyle.color.normal = ['', ''];
        }
        
        if ( !CanWeather.simpleStorage.storage.options.toolbarDisplay.showConditionIcon ) {
            CanWeather.simpleStorage.storage.options.toolbarDisplay.showConditionIcon = false;
        }
        
        if ( !CanWeather.simpleStorage.storage.options.toolbarDisplay.showConditionIconTooltip ) {
            CanWeather.simpleStorage.storage.options.toolbarDisplay.showConditionIconTooltip = false;
        }
    }

    CanWeather.defaultImageUrl = CanWeather.data.url('images/condition/99.png');
    CanWeather.createPanels();
    CanWeather.createWidget();

    if ( CanWeather.simpleStorage.storage.options.location.siteCode ) {
        // fetchWeatherData() also creates the widget, using image or text content as determined in options.
        CanWeather.fetchWeatherData();
        CanWeather.timerId = CanWeather.timers.setInterval(function() { CanWeather.fetchWeatherData(); }, CanWeather.simpleStorage.storage.options.frequencyMilli);
    }
    
    // Workaround for Bug 666117 - Widget content destroyed after open/close of Customize Toolbar dialog. 
    // Causing browser crashes? Back out.
    //CanWeather.observerService.add('dom-window-destroyed', CanWeather.domWindowClosed);
};

exports.onUnload = function (aReason) {
    if ( aReason == 'uninstall' || aReason == 'disable' ) {
        CanWeather.myWidget.destroy();
    }
    
    //CanWeather.observerService.remove('dom-window-destroyed', CanWeather.domWindowClosed);
};